package org.teamh.gui;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.util.glu.GLU.gluPerspective;

import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.concurrent.Semaphore;

import org.teamh.game.StartGame;
import org.teamh.game.repository.MapRepository;
import org.teamh.gui.engine.OpenGLView;
import org.teamh.gui.engine.model.ModelLoader;
import org.teamh.gui.ingame.IngameScreen;
import org.teamh.gui.loadingscreen.LoadingScreen;
import org.teamh.gui.lobby.LobbyScreen;
import org.teamh.gui.login.LoginScreen;
import org.teamh.gui.mapeditor.MapEditorScreen;

import de.lessvoid.nifty.spi.render.RenderFont;

/**
 * This class is the graphical user interface for the client. It handles the
 * basic openGL rendering and the switching of the screens. It is also
 * responsible for the creating of the screens and the hud.
 * 
 * @author richard
 * 
 */
public class GUI extends OpenGLView implements InterfaceManager{

	private StartGame start;
	private Screen currentScreen;

	private LoginScreen loginScreen;
	private LobbyScreen	lobbyScreen;
	private LoadingScreen loadingScreen;
	private MapEditorScreen mapEditorScreen;
	private IngameScreen ingameScreen;
	
	private Screen followingScreen;
	
	private LinkedList<Event> eventQueue = new LinkedList<Event>();
	
	private Semaphore lockScreenRequest;
	private String asyncGuiRequestName;

	public GUI(StartGame start) {
    	super("CreepSmash", false, 1024, 768);

		this.start = start;
		
		lockScreenRequest = new Semaphore(1);
	}
	
	/**
	 * This method is called, when the application starts and builds the thread
	 * for the gui.
	 */
	public void start() {
		new Thread(this).start();
	}
	
	public StartGame getStart() {
		return start;
	}
	
	/**
	 * This method is called while the init process of the openGL window takes
	 * place. Any openGL specific initialization should be inserted here.
	 */
	@Override
	public void init() {
		initNifty();
		
		getNifty().loadStyleFile("nifty-default-styles.xml");
	    getNifty().loadControlFile("nifty-default-controls.xml");
	    getNifty().registerEffect("squizzle", "org.teamh.gui.engine.effects.SquizzleEffect");
	    
	    RenderFont font = getNifty().getRenderEngine().createFont("verdana-48-regular.fnt", 0.5f);
	    getNifty().getRenderEngine().setFont(font);
	    
	    loginScreen = new LoginScreen(this);
		lobbyScreen = new LobbyScreen(this);
		loadingScreen = new LoadingScreen(this);
		mapEditorScreen = new MapEditorScreen(this);
		ingameScreen = new IngameScreen(this);
		loginScreen.build();
		lobbyScreen.build();
		loadingScreen.build();
		mapEditorScreen.build();
		ingameScreen.build();
		
		glClearColor(0.0f, 0.0f, 0.2f, 1.0f);
		glEnable(GL_DEPTH_TEST);
		
		setFollowingScreen(loginScreen);
		
	}

	/**
	 * This method is called while the init process directly after the window
	 * has been created. Here all the information for window size etc. should
	 * take place.
	 */
	@Override
	public void reshape() {
		float ratio = (float)getWidth() / getHeight();
		int w = 0;
		int h = 0;
		
		if(ratio > 4f / 3f) {
			w = (int)(getHeight() * 4f / 3f);
			h = getHeight();
		} else {
			w = getWidth();
			h = (int)(getWidth() * 3f / 4f);
		}
		
		glViewport((getWidth() - w) / 2, (getHeight() - h) / 2, w, h);
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		gluPerspective(45.0f, 4f / 3f, 0.1f, 50.0f);
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
	}

	/**
	 * This switches the current scene. When switching the new screen is
	 * initialized.
	 * 
	 * @param currentScreen
	 */
	public void setCurrentScreen(Screen currentScreen) {
		this.currentScreen = currentScreen;
	}
	
	public void setFollowingScreen(final Screen followingScreen) {
		try {
			lockScreenRequest.acquire();
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				String waitFor = followingScreen.getNiftyScreenID();
				asyncGuiRequestName = waitFor;
				followingScreen.init();
				
				while(!waitFor.equals(getNifty().getCurrentScreen().getScreenId())) {
					try {
						Thread.sleep(20);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
				lockScreenRequest.release();
				setFollowingScreenPriv(followingScreen);
			}
		}).start();
	}
	
	private void setFollowingScreenPriv(Screen followingScreen) {
		this.followingScreen = followingScreen;
	}

	@Override
	public void render(float timeElapsed) {
		if(asyncGuiRequestName != null) {
			getNifty().gotoScreen(asyncGuiRequestName);
			asyncGuiRequestName = null;
		}
		
		if(followingScreen != null) {
			setCurrentScreen(followingScreen);
			
			followingScreen = null;
		}
		
		if(currentScreen != null) {
			currentScreen.render(timeElapsed);
			
			if(currentScreen.canPoll()) {
				try {
					while(eventQueue.size() > 0) {
					currentScreen.eventOccured(eventQueue.poll());
					}
				} catch (NoSuchElementException e) {
					System.out.println("nosuchelement exception");
				}
					
			}
		}
	}

	@Override
	public void mouseDragged(int posX, int posY, int button) {
		if(currentScreen != null) {
			currentScreen.mouseDragged(posX, posY, button);
		}
	}

	@Override
	public void mouseMoved(int posX, int posY, int button) {
		if(currentScreen != null) {
			currentScreen.mouseMoved(posX, posY, button);
		}
	}

	@Override
	public void mousePressed(int posX, int posY, int button) {
		if(currentScreen != null) {
			currentScreen.mousePressed(posX, posY, button);
		}
	}

	@Override
	public void mouseReleased(int posX, int posY, int button) {
		if(currentScreen != null) {
			currentScreen.mouseReleased(posX, posY, button);
		}
	}

	@Override
	public void mouseWheelMoved(int movement) {
		if(currentScreen != null) {
			currentScreen.mouseWheelMoved(movement);
		}
	}

	@Override
	public void keyPressed(int key) {
		if(currentScreen != null) {
			currentScreen.keyPressed(key);
		}
	}

	@Override
	public void keyReleased(int key) {
		if(currentScreen != null) {
			currentScreen.keyReleased(key);
		}
	}
	
	@Override
	public void dealloc() {
		start.exit();
		loginScreen.getBackGroundTimer().cancel();
		loginScreen.dealloc();
		lobbyScreen.dealloc();
		loadingScreen.dealloc();
		mapEditorScreen.dealloc();
		ingameScreen.dealloc();
		
		MapRepository.clear();
		ModelLoader.dealloc();
	}
	
	public void addEvent(Event event) {
		eventQueue.offer(event);
	}
	
	public Screen getCurrentScreen() {
		return currentScreen;
	}
	
	public LoadingScreen getLoadingScreen() {
		return loadingScreen;
	}
	
	public LoginScreen getLoginScreen() {
		return loginScreen;
	}
	
	public LobbyScreen getLobbyScreen() {
		return lobbyScreen;
	}
	
	public MapEditorScreen getMapEditorScreen() {
		return mapEditorScreen;
	}

	public IngameScreen getIngameScreen() {
		return ingameScreen;
	}

	@Override
	public void interrupt() {
		// TODO Auto-generated method stub
		
	}
}
